﻿/******************************************************************** 
 * Cerebrum Embedded System Design Automation Framework
 * Copyright (C) 2010  The Pennsylvania State University
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ********************************************************************/
/************************************************************************************************************
 * MultiJProgrammer.cs
 * Name: Matthew Cotter
 * Date: 21 Oct 2010 
 * Description: This class wraps the JProgrammer library to facilitate all-at-once programming of FPGAs.
 * Notes:
 * History: 
 * >> (21 Oct 2010) Matthew Cotter: Moved MultiJProgrammer class from FalconJTAGProgrammerTool project.
 * >> (13 Aug 2010) Matthew Cotter: Updated code that handles loading of Platform files to use new hierarchical location and format of platforms 
 *                                    (paths.ProjectPlatform -> paths.Platforms\<Platform>\<Platform.xml> -> 
 *                                      paths.Platforms\<Platform>\<Board>\<Board>.xml -> paths.Platforms\<Platform>\<Board>\<fpga>\<fpga>.xml.
 *                                  Removed platform file from cerebinput command due to new hierarchical location and format of platforms.
 * >> ( 2 Aug 2010) Matthew Cotter: Added some support for programming multiple processors.
 *                                  Added code to read processor options from processors section of design, as it may be 
 *                                      specific to the processor, rather than the board.   Cable information is still read from the Programming section.
 * >> (21 Jul 2010) Matthew Cotter: Verified that XML-generated Documentation is current.
 * >> ( 6 Jul 2010) Matthew Cotter: Added some cleanup to remove stale files from previous runs in programming folder.
 *                                   Updated LoadPlatformFile(), ProcessPlatforms(), and ProcessBoard() to handle hierarchical format of system platform.
 * >> ( 5 Jul 2010) Matthew Cotter: Significant internal changes made to JProgrammer class in order to support correct identification and programming of target board.
 * >> ( 1 Jul 2010) Matthew Cotter: Updated Multi-JProgrammer class to correctly reset and set options per platform being programmed.
 * >> (30 Jun 2010) Matthew Cotter: Started work on Creating Multi-JProgrammer wrapper class for programming multiple boards in a single invocation.
 * >> ( 1 Jun 2010) Matthew Cotter: Source file created -- Initial version.
 ***********************************************************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FalconGlobal;
using FalconPathManager;
using System.Xml;
using System.IO;
using System.Diagnostics;
using CerebrumSharedClasses;
using System.Threading;

namespace FalconJTAG_Programmer
{
    /// <summary>
    /// Class to encapsulate the JProgrammer class in order to facilitate the all-at-once programming of multiple FPGAs.
    /// </summary>
    public class MultiJProgrammer
    {
        /// <summary>
        /// Event fired when a message has been generated by the library
        /// </summary>
        public event MessageEventDelegate MessageEvent;
        /// <summary>
        /// Raises an event indicating that a message has been generated.
        /// </summary>
        /// <param name="Message">The message to be transmitted</param>
        public void RaiseMessageEvent(string Message)
        {
            if (MessageEvent != null)
            {
                MessageEvent(Message);
            }
            else
            {
                Console.WriteLine(Message);
            }
        }

        /// <summary>
        /// Event that fires to request a password from an external source.  If this event is not attached,
        /// the password is requested from the console.
        /// </summary>
        public PasswordRequestDelegate OnRequirePassword;


        private static JProgrammer JProg;

        private LinkedList<FalconServer> _Servers;
        private LinkedListNode<FalconServer> _nextServer;
        private FalconServer GetNextServer()
        {
            if (_nextServer == null)
            {
                _nextServer = _Servers.First;
            }
            else
            {
                _nextServer = _nextServer.Next;
                if (_nextServer == null)
                    _nextServer = _Servers.First;
            }

            if (_nextServer != null)
                return _nextServer.Value;
            else
                return null;
        }
        private PathManager _PathMan;
        private List<JPPlatform> _Platforms;

        private class ProcessorProgramInfo
        {
            public ProcessorProgramInfo()
            {
            }

            public string Instance { get; set; }
            public JProgrammer.ProcessorType Type { get; set; }
            public string CPUNumber { get; set; }
        }

        private class JPPlatform
        {
            public JPPlatform()
            {
                this.Processors = new List<ProcessorProgramInfo>();
            }
            public string PlatformID { get; set; }
            public uint JTAG { get; set; }
            public string CableType { get; set; }
            public string CablePort { get; set; }
            public List<ProcessorProgramInfo> Processors;
        }
        /// <summary>
        /// Default constructor.  Initializes internal JProgrammer object.
        /// </summary>
        public MultiJProgrammer()
        {
            JProg = new JProgrammer();
            JProg.MessageEvent += new MessageEventDelegate(RaiseMessageEvent);
        }

        /// <summary>
        /// Reads the password for the locally specified programming server from the Console.  The password characters are not echoed to the console, but replaced by asterisks.
        /// </summary>
        /// <param name="user">The user name to be included in the prompt.</param>
        /// <param name="server">The server address to included in the prompt.</param>
        /// <returns>The password as read in from the Console.</returns>
        public string ReadPassword(string user, string server)
        {
            Console.WriteLine("{0}@{1}'s password: ", user, server);
            string input = string.Empty;
            string rd;
            rd = Console.ReadKey(true).KeyChar.ToString();
            while ((rd != "\n") && (rd != "\r"))
            {
                if (rd == "\b")
                {
                    if (input.Length > 0)
                    {
                        input = input.Substring(0, input.Length - 1);
                        Console.CursorLeft = Console.CursorLeft - 1;
                        Console.Write(" ");
                        Console.CursorLeft = Console.CursorLeft - 1;
                    }
                }
                else
                {
                    input += rd;
                    RaiseMessageEvent("*");
                }
                rd = Console.ReadKey(true).KeyChar.ToString();
            }
            input = input.Replace("\r", string.Empty);
            input = input.Replace("\n", string.Empty);
            Console.WriteLine();
            return input;
        }

        private void ConnectTo(FalconServer PServer)
        {
            JProg.ClearOptions();
            JProg.Username = PServer.UserName;
            JProg.Servername = PServer.Address;
            if (PServer.Password == string.Empty)
            {
                if (OnRequirePassword != null)
                {
                    JProg.UserPass = OnRequirePassword(PServer.UserName, PServer.Address);
                }
                else
                {
                    JProg.UserPass = ReadPassword(PServer.UserName, PServer.Address);
                } 
            }
            else
            {
                JProg.UserPass = PServer.Password;
            }
            JProg.login();
        }

        private void Disconnect()
        {
            try
            {
                JProg.logout();
            }
            catch (Exception ex)
            { Debug.WriteLine(ex.Message); }
        }

        /// <summary>
        /// Loads an XML file specifying the synthesis servers to be used for synthesis.
        /// </summary>
        /// <param name="ServerFile">The path to the XML file specifying the server list.</param>
        /// <returns>True if the loading was successful, False otherwise.</returns>
        public bool LoadServersFile(string ServerFile)
        {
            _Servers = new LinkedList<FalconServer>();
            try
            {
                if (!File.Exists(ServerFile))
                    ServerFile = _PathMan.GetPath("LocalProjectRoot") + "\\" + ServerFile;
                if (!File.Exists(ServerFile))
                    return false;
                XmlDocument xDoc = new XmlDocument();
                xDoc.Load(ServerFile);
                RaiseMessageEvent(String.Format("Loaded Servers from: {0}", ServerFile));

                foreach (XmlNode xElem in xDoc.ChildNodes)
                {
                    if (xElem.Name.ToLower() == "servers")
                    {
                        ReadServers(xElem);
                        break;
                    }
                }

            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
                return false;
            }
            return true;
        }
        private void ReadServers(XmlNode ServersNode)
        {
            foreach (XmlNode xEServer in ServersNode.ChildNodes)
            {
                if (xEServer.Name.ToLower() == "server")
                {
                    FalconServer fs = new FalconServer();
                    fs.ParseServerNode(xEServer);
                    _Servers.AddLast(fs);
                }
            }
        }
        /// <summary>
        /// Loads an XML file specifying the design to be programmed.
        /// </summary>
        /// <param name="DesignFile">The path to the XML file specifying the design.</param>
        /// <returns>True if the loading was successful, False otherwise.</returns>
        public bool LoadDesignFile(string DesignFile)
        {
            try
            {
                if (!File.Exists(DesignFile))
                    DesignFile = _PathMan.GetPath("LocalProjectRoot") + "\\" + DesignFile;
                if (!File.Exists(DesignFile))
                    return false;
                XmlDocument xDoc = new XmlDocument();
                xDoc.Load(DesignFile);
                RaiseMessageEvent(String.Format("Loaded Design from: {0}", DesignFile));
                foreach (XmlNode xElem in xDoc.ChildNodes)
                {
                    if (xElem.Name.ToLower() == "design")
                    {
                        foreach (XmlNode xDesNode in xElem.ChildNodes)
                        {
                            if (xDesNode.Name.ToLower() == "programming")
                            {
                                foreach (XmlNode xProcNode in xDesNode.ChildNodes)
                                {
                                    if (xProcNode.Name.ToLower() == "program")
                                    {
                                        string deviceID = string.Empty;
                                        foreach (XmlAttribute xAttr in xProcNode.Attributes)
                                        {
                                            if (xAttr.Name.ToLower() == "device")
                                            {
                                                deviceID = xAttr.Value;
                                            }
                                        }
                                        JPPlatform jpf = null;
                                        foreach (JPPlatform jplat in _Platforms)
                                        {
                                            if (jplat.PlatformID == deviceID)
                                            {
                                                jpf = jplat;
                                                break;
                                            }
                                        }
                                        if (jpf != null)
                                        {
                                            foreach (XmlNode xProcProp in xProcNode.ChildNodes)
                                            {
                                                if (xProcProp.Name.ToLower() == "cabletype")
                                                {
                                                    jpf.CableType = xProcProp.InnerText;
                                                }
                                                else if (xProcProp.Name.ToLower() == "cableport")
                                                {
                                                    jpf.CablePort = xProcProp.InnerText;
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                            else if (xDesNode.Name.ToLower() == "processors")
                            {
                                foreach (XmlNode xProcNode in xDesNode.ChildNodes)
                                {
                                    if (String.Compare(xProcNode.Name, "processor", true) == 0)
                                    {
                                        string deviceID = string.Empty;
                                        ProcessorProgramInfo PPI = new ProcessorProgramInfo();
                                        PPI.Instance = string.Empty;
                                        foreach (XmlAttribute xAttr in xProcNode.Attributes)
                                        {
                                            if (String.Compare(xAttr.Name, "Instance", true) == 0)
                                            {
                                                PPI.Instance = xAttr.Value;
                                            }
                                        }
                                        if (PPI.Instance != string.Empty)
                                        {
                                            foreach (XmlNode xProcProp in xProcNode.ChildNodes)
                                            {
                                                if (String.Compare(xProcProp.Name, "FPGA", true) == 0)
                                                {
                                                    deviceID = xProcProp.InnerText;
                                                }
                                                else if (String.Compare(xProcProp.Name, "type", true) == 0)
                                                {
                                                    string ptype = xProcProp.InnerText.ToLower();
                                                    if ((ptype.Contains("ppc")) || (ptype.Contains("powerpc")))
                                                        PPI.Type = JProgrammer.ProcessorType.PPC;
                                                    else if ((ptype.Contains("microblaze")) || (ptype.Contains("mb")))
                                                        PPI.Type = JProgrammer.ProcessorType.MB;
                                                    else
                                                        PPI.Type = JProgrammer.ProcessorType.PPC;   // Default
                                                }
                                                else if (String.Compare(xProcProp.Name, "cpunumber", true) == 0)
                                                {
                                                    PPI.CPUNumber = xProcProp.InnerText;
                                                }
                                            }

                                            // Find the associated platform
                                            JPPlatform jpf = null;
                                            foreach (JPPlatform jplat in _Platforms)
                                            {
                                                if (String.Compare(jplat.PlatformID, deviceID) == 0)
                                                {
                                                    jpf = jplat;
                                                    break;
                                                }
                                            }
                                            if (jpf != null)
                                            {
                                                if ((PPI.CPUNumber == null) || (PPI.CPUNumber == string.Empty))
                                                {
                                                    PPI.CPUNumber = "1";
                                                }
                                                jpf.Processors.Add(PPI);
                                            }
                                        }
                                    }
                                }
                            }
                        }

                        return true;
                    }
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
                return false;
            }
            return true;
        }

        /// <summary>
        /// Loads the individual FPGA platforms from the master platform file.
        /// </summary>
        /// <returns>True if loading the platforms was successful, false otherwise.</returns>
        /// <seealso href="https://www.cse.psu.edu/svn/mdl/falcon_repository/trunk/Software/Cerebrum/Documentation/ProjectXML.pdf">
        /// Project XML File Documentation (Cerebrum Project Files, Platform)</seealso>
        /// <seealso href="https://www.cse.psu.edu/svn/mdl/falcon_repository/trunk/Software/Cerebrum/Documentation/Platform XML Specifications.pdf">
        /// Platform XML File Documentation</seealso>
        public bool LoadPlatformFile()
        {
            try
            {
                FileInfo PlatformFile = new FileInfo(String.Format(@"{0}\{1}\{1}.xml", _PathMan["Platforms"], _PathMan["ProjectPlatform"]));
                if (!PlatformFile.Exists)
                    return false;
                XmlDocument xDoc = new XmlDocument();
                xDoc.Load(PlatformFile.FullName);
                _Platforms = new List<JPPlatform>();
                _Platforms.Clear();

                foreach (XmlNode xElem in xDoc.ChildNodes)
                {
                    if (xElem.Name.ToLower() == "platform")
                    {
                        foreach (XmlNode xBoardNode in xElem.ChildNodes)
                        {
                            if (xBoardNode.Name.ToLower() == "board")
                            {
                                string boardFile = string.Empty;
                                string boardID = string.Empty;
                                foreach (XmlAttribute xAttr in xBoardNode.Attributes)
                                {
                                    if (xAttr.Name.ToLower() == "file")
                                    {
                                        boardFile = xAttr.Value;
                                    }
                                    else if (xAttr.Name.ToLower() == "id")
                                    {
                                        boardID = xAttr.Value;
                                    }
                                }
                                if ((boardID != string.Empty) && (boardFile != string.Empty))
                                {
                                    ProcessBoard(boardID, boardFile);
                                }
                            }
                        }
                    }
                }
                RaiseMessageEvent(String.Format("Loaded Platform from: {0}", PlatformFile));
                return true;
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
                return false;
            }
        }
        private void ProcessBoard(string boardID, string BoardFileName)
        {
            string BoardShortName = BoardFileName.Substring(0, BoardFileName.IndexOf("."));
            FileInfo BoardFile = new FileInfo(String.Format(@"{0}\{1}\{2}\{2}.xml", _PathMan["Platforms"], _PathMan["ProjectPlatform"], BoardShortName));
            if (!BoardFile.Exists)
                return;

            XmlDocument xDoc = new XmlDocument();
            xDoc.Load(BoardFile.FullName);

            foreach (XmlNode xElem in xDoc.ChildNodes)
            {
                if (xElem.Name.ToLower() == "board")
                {
                    foreach (XmlNode xBoardNode in xElem.ChildNodes)
                    {
                        if (xBoardNode.Name.ToLower() == "fpga")
                        {
                            string fpgaFile = string.Empty;
                            string fpgaID = string.Empty;
                            foreach (XmlAttribute xAttr in xBoardNode.Attributes)
                            {
                                if (xAttr.Name.ToLower() == "file")
                                {
                                    fpgaFile = xAttr.Value;
                                }
                                else if (xAttr.Name.ToLower() == "id")
                                {
                                    fpgaID = xAttr.Value;
                                }
                            }
                            if ((fpgaID != string.Empty) && (fpgaFile != string.Empty))
                            {
                                ProcessFPGA(boardID, BoardShortName, fpgaID, fpgaFile);
                            }
                        }
                    }
                }
            }
        }
        private void ProcessFPGA(string boardID, string BoardShortName, string FPGAID, string FPGAFileName)
        {
            string FPGAShortName = FPGAFileName.Substring(0, FPGAFileName.IndexOf("."));
            FileInfo FPGAFile = new FileInfo(String.Format(@"{0}\{1}\{2}\{3}\{3}.xml", _PathMan["Platforms"], _PathMan["ProjectPlatform"], BoardShortName, FPGAShortName));
            if (!FPGAFile.Exists)
                return;

            XmlDocument xDoc = new XmlDocument();
            xDoc.Load(FPGAFile.FullName);
            foreach (XmlNode xNode in xDoc.ChildNodes)
            {
                if (xNode.Name.ToLower() != "xml")
                {
                    foreach (XmlNode xBoardItem in xNode.ChildNodes)
                    {
                        if (xBoardItem.Name.ToLower() == "fpga")
                        {
                            JPPlatform jpf = new JPPlatform();
                            jpf.PlatformID = boardID + "." + FPGAID;

                            // Get Key information
                            foreach (XmlAttribute xAttr in xBoardItem.Attributes)
                            {
                                if (xAttr.Name.ToLower() == "jtag_position")
                                {
                                    uint val;
                                    if (uint.TryParse(xAttr.Value, out val))
                                    {
                                        jpf.JTAG = val;
                                    }
                                    else
                                    {
                                        throw new Exception("Error reading JTAG number from programming specification.");
                                    }
                                }
                            }
                            _Platforms.Add(jpf);
                        }
                    }
                }
            }
        }


        /// <summary>
        /// Loads an XML file specifying the design to be synthesized.
        /// </summary>
        /// <param name="PathFile">The path to the XML file specifying the project paths.</param>
        /// <returns>True if the loading was successful, False otherwise.</returns>
        public void LoadPaths(string PathFile)
        {
            try
            {
                _PathMan = new PathManager(PathFile);
                RaiseMessageEvent(String.Format("Loaded Paths from: {0}", PathFile));
            }
            catch (Exception ex)
            {
                RaiseMessageEvent(ex.Message);
            }
        }

        /// <summary>
        /// Programs each FPGA in the the multi-FPGA system using the loaded information.
        /// </summary>
        /// <returns>True if the programming was successful for all platforms, False otherwise.</returns>
        public bool ProgramSystem()
        {
            try
            {
                FalconServer PServer = GetNextServer();
                if (PServer == null)
                {
                    return false;
                }
                else
                {
                    ConnectTo(PServer);
                }

                string RDir = string.Empty;
                string[] Dirs = _PathMan.GetPath("ProgrammingPath").Split('/');
                for (int i = 1; i < Dirs.Length; i++)
                {
                    RDir = String.Format("{0}/{1}", RDir, Dirs[i]);
                    JProg.Exec(String.Format("mkdir {0}", RDir));
                }


                foreach (JPPlatform jpf in _Platforms)
                {
                    string PlatformID = jpf.PlatformID;
                    string LocalProgPath = string.Empty;
                    string RemoteProgPath = string.Empty;

                    // Setup the JProgrammer
                    JProg.ClearOptions();
                    JProg.isLocalProgrammer = false;
                    JProg.localOSType = JProgrammer.OSVersion.WINDOWS;
                    JProg.remoteOSType = (PServer.LinuxHost ? JProgrammer.OSVersion.LINUX : JProgrammer.OSVersion.WINDOWS);
                    JProg.createFolder_ifNotExist = true;
                    JProg.overWriteFiles = true;
                    JProg.impactModeOpt = "-bs"; // Boundary-Scan Mode
                    JProg.CablePort = jpf.CablePort;
                    JProg.CableType = jpf.CableType;
                    JProg.JTAG = jpf.JTAG;
                    if (String.Compare(JProg.CablePort, string.Empty) == 0)
                    {
                        RaiseMessageEvent(String.Format("WARNING: Cable Port not set for platform '{0}' -- Skipping Programming", PlatformID));
                        continue;
                    }
                    // Set properties read from platform
                    LocalProgPath = _PathMan.GetPath("LocalProject") + "\\" + jpf.PlatformID + "\\output";
                    if (!Directory.Exists(LocalProgPath))
                        Directory.CreateDirectory(LocalProgPath);
                    RemoteProgPath = _PathMan.GetPath("ProgrammingPath") + "/" + PlatformID;

                    JProg.Exec(String.Format("mkdir {0}", RemoteProgPath));

                    JProg.Set_Local_Dir(LocalProgPath);
                    JProg.Set_Remote_Dir(_PathMan.GetPath("ProgrammingPath"));
                    JProg.Set_Remote_Dir(RemoteProgPath);

                    FileInfo localCMDFile = new FileInfo(LocalProgPath + "\\" + PlatformID + ".cmd");
                    FileInfo localTCLFile = new FileInfo(LocalProgPath + "\\" + PlatformID + ".tcl");
                    FileInfo localBITFile = new FileInfo(LocalProgPath + "\\" + PlatformID + "_system.bit");
                    FileInfo localELFFile = new FileInfo(LocalProgPath + "\\dummy.elf"); // Dummy file name

                    // Set local file names for generation
                    JProg.cmdFileName = localCMDFile.Name;
                    JProg.tclFileName = localTCLFile.Name;
                    JProg.bitFileName = localBITFile.Name;
                    JProg.elfFileName = localELFFile.Name;

                    string remoteCMDFile = RemoteProgPath + "/" + localCMDFile.Name;
                    string remoteTCLFile = RemoteProgPath + "/" + localTCLFile.Name;
                    string remoteBITFile = RemoteProgPath + "/" + localBITFile.Name;
                    string remoteELFFile = RemoteProgPath + "/" + localELFFile.Name;

                    // Copy bit/elf files to programming folder
                    if (!localBITFile.Exists)
                    {
                        RaiseMessageEvent(String.Format("WARNING: BIT file {1} not found for platform '{0}' -- Skipping Programming", PlatformID, localBITFile.Name));
                        continue;
                    }

                    // Add programming commands to CMD file
                    JProg.ClearCMDfile();
                    JProg.Clean_iMPACT_Cable();
                    JProg.DownloadBitFile(localBITFile.Name);
                    JProg.Clean_iMPACT_Cable();
                    JProg.Generate_cmd_File();

                    // Verify all ELF files exist
                    foreach (ProcessorProgramInfo PPI in jpf.Processors)
                    {
                        localELFFile = new FileInfo(LocalProgPath + "\\" + PlatformID + "_" + PPI.Instance + ".elf");
                        if (!localELFFile.Exists)
                        {
                            RaiseMessageEvent(String.Format("WARNING: ELF file {2} not found for platform '{0}', processor '{1}' -- Skipping Programming", PlatformID, PPI.Instance, localELFFile.Name));
                            continue;
                        }
                    }




                    // Send Files                                
                    RaiseMessageEvent(String.Format("Transferring '{0}' files to programming server...", PlatformID));

                    // Send BIT File
                    JProg.Set_Local_Dir(localBITFile.Directory.FullName);
                    JProg.Set_Remote_Dir(remoteBITFile.Replace(localBITFile.Name, string.Empty));
                    JProg.Copy_File_To(localBITFile.Name);

                    // Send all ELF File(s)
                    foreach (ProcessorProgramInfo PPI in jpf.Processors)
                    {
                        localELFFile = new FileInfo(LocalProgPath + "\\" + PlatformID + "_" + PPI.Instance + ".elf");
                        if (!localELFFile.Exists)
                        {
                            RaiseMessageEvent(String.Format("WARNING: ELF file {2} not found for platform '{0}', processor '{1}' -- Skipping Programming", PlatformID, PPI.Instance, localELFFile.Name));
                            continue;
                        }
                        JProg.Set_Local_Dir(localELFFile.Directory.FullName);
                        JProg.Copy_File_To(localELFFile.Name);
                    }

                    // Copy the CMD File for iMPACT
                    JProg.Set_Local_Dir(localCMDFile.Directory.FullName);
                    JProg.Copy_File_To(localCMDFile.Name);
                    RaiseMessageEvent("Complete!");

                    // Program BIT File
                    RaiseMessageEvent("Downloading BIT File to FPGA...");
                    if (!JProg.runImpactProg(jpf.PlatformID))
                    {
                        RaiseMessageEvent(String.Format("An error occurred running iMPACT to download the configuration BIT file: {0}!", localBITFile.Name));
                        Disconnect();
                        return false;
                    }
                    RaiseMessageEvent("Complete!");

                    // Program ELF File(s)
                    RaiseMessageEvent("Downloading ELF File(s) to FPGA...");
                    foreach (ProcessorProgramInfo PPI in jpf.Processors)
                    {
                        localELFFile = new FileInfo(LocalProgPath + "\\" + PlatformID + "_" + PPI.Instance + ".elf");
                        localTCLFile = new FileInfo(LocalProgPath + "\\" + PlatformID + "_" + PPI.Instance + ".tcl");
                        JProg.Set_Local_Dir(localTCLFile.Directory.FullName);
                        JProg.tclFileName = localTCLFile.Name;
                        JProg.ClearTCLfile();
                        JProg.Clean_XMD_Cable();
                        JProg.elfFileName = localELFFile.Name;
                        JProg.PCType = PPI.Type;
                        JProg.DownloadElfFile(PPI.CPUNumber, localELFFile.Name);
                        JProg.Clean_XMD_Cable();
                        JProg.Generate_tcl_file();

                        if (!localELFFile.Exists)
                        {
                            RaiseMessageEvent(String.Format("WARNING: ELF file {2} not found for platform '{0}', processor '{1}' -- Skipping Programming", PlatformID, PPI.Instance, localELFFile.Name));
                            continue;
                        }
                        // Copy the TCL File for XMD
                        JProg.Copy_File_To(localTCLFile.Name);
                        RaiseMessageEvent(String.Format("  Proc: {0}; File: {1}...", PPI.Instance, localELFFile.Name));

                        if (!JProg.runXMDProg(jpf.PlatformID, PPI.Instance))
                        {
                            RaiseMessageEvent(String.Format("An error occurred running XMD to download the ELF file: {0}!", localELFFile.Name));
                            Disconnect();
                            return false;
                        }
                        RaiseMessageEvent("Complete!");
                        if (localTCLFile.Exists) localTCLFile.Delete();
                    }
                    RaiseMessageEvent("Complete!");

                    // Programming complete, try to remove the local files so there aren't any stale files for the next run
                    if (localCMDFile.Exists) localCMDFile.Delete();
                }
                Disconnect();
                return true;
            }
            catch (ThreadAbortException TAEx)
            {
                ErrorReporting.ExceptionDetails(TAEx);
                return false;
            }
            catch (Exception ex)
            {
                RaiseMessageEvent(ex.Message);
                ErrorReporting.DebugException(ex);
                return false;
            }
            finally
            {
                Disconnect();
            }
        }
    }

}
